/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.corba.se.impl.activation;

/**
 *
 * @author      Rohit Garg
 * @author      Ken Cavanaugh
 * @author      Hemanth Puttaswamy
 * @since       JDK1.2
 */

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.NoSuchElementException;

import org.omg.CORBA.OBJECT_NOT_EXIST;
import org.omg.CORBA.SystemException;

import com.sun.corba.se.spi.activation.EndPointInfo;
import com.sun.corba.se.spi.activation.IIOP_CLEAR_TEXT;
import com.sun.corba.se.spi.activation.ORBPortInfo;
import com.sun.corba.se.spi.activation.Repository;
import com.sun.corba.se.spi.activation.LocatorPackage.ServerLocation;
import com.sun.corba.se.spi.activation.LocatorPackage.ServerLocationPerORB;
import com.sun.corba.se.spi.activation.RepositoryPackage.ServerDef;
import com.sun.corba.se.spi.activation._ServerManagerImplBase;
import com.sun.corba.se.spi.activation.ServerAlreadyActive;
import com.sun.corba.se.spi.activation.ServerAlreadyInstalled;
import com.sun.corba.se.spi.activation.ServerAlreadyUninstalled;
import com.sun.corba.se.spi.activation.ServerNotRegistered;
import com.sun.corba.se.spi.activation.ORBAlreadyRegistered;
import com.sun.corba.se.spi.activation.ServerHeldDown;
import com.sun.corba.se.spi.activation.ServerNotActive;
import com.sun.corba.se.spi.activation.NoSuchEndPoint;
import com.sun.corba.se.spi.activation.InvalidORBid;
import com.sun.corba.se.spi.activation.Server;
import com.sun.corba.se.spi.activation.IIOP_CLEAR_TEXT;
import com.sun.corba.se.spi.ior.IORTemplate;
import com.sun.corba.se.spi.ior.IOR;
import com.sun.corba.se.spi.ior.ObjectKey;
import com.sun.corba.se.spi.ior.ObjectKeyTemplate;
import com.sun.corba.se.spi.ior.IORFactories;
import com.sun.corba.se.spi.ior.iiop.GIOPVersion;
import com.sun.corba.se.spi.ior.iiop.IIOPAddress;
import com.sun.corba.se.spi.ior.iiop.IIOPProfileTemplate;
import com.sun.corba.se.spi.ior.iiop.IIOPFactories;
import com.sun.corba.se.spi.legacy.connection.LegacyServerSocketEndPointInfo;
import com.sun.corba.se.spi.transport.SocketOrChannelAcceptor;
import com.sun.corba.se.spi.orb.ORB;
import com.sun.corba.se.spi.protocol.ForwardException;
import com.sun.corba.se.spi.transport.CorbaTransportManager;

import com.sun.corba.se.spi.logging.CORBALogDomains;
import com.sun.corba.se.impl.logging.ActivationSystemException;

import com.sun.corba.se.impl.oa.poa.BadServerIdHandler;
import com.sun.corba.se.impl.orbutil.ORBConstants;
import com.sun.corba.se.impl.orbutil.ORBUtility;
import com.sun.corba.se.impl.util.Utility;

public class ServerManagerImpl extends _ServerManagerImplBase implements BadServerIdHandler {
    // Using HashMap, since synchronization should be done by the calling
    // routines
    HashMap serverTable;
    Repository repository;

    CorbaTransportManager transportManager;
    int initialPort;
    ORB orb;
    ActivationSystemException wrapper;
    String dbDirName;
    boolean debug = false;

    private int serverStartupDelay;

    ServerManagerImpl(ORB orb, CorbaTransportManager transportManager, Repository repository, String dbDirName, boolean debug) {
        this.orb = orb;
        wrapper = ActivationSystemException.get(orb, CORBALogDomains.ORBD_ACTIVATOR);

        this.transportManager = transportManager; // REVISIT - NOT USED.
        this.repository = repository;
        this.dbDirName = dbDirName;
        this.debug = debug;

        LegacyServerSocketEndPointInfo endpoint = orb.getLegacyServerSocketManager().legacyGetEndpoint(LegacyServerSocketEndPointInfo.BOOT_NAMING);

        initialPort = ((SocketOrChannelAcceptor) endpoint).getServerSocket().getLocalPort();
        serverTable = new HashMap(256);

        // The ServerStartupDelay is the delay added after the Server registers
        // end point information. This is to allow the server to completely
        // initialize after ORB is instantiated.
        serverStartupDelay = ORBConstants.DEFAULT_SERVER_STARTUP_DELAY;
        String delay = System.getProperty(ORBConstants.SERVER_STARTUP_DELAY);
        if (delay != null) {
            try {
                serverStartupDelay = Integer.parseInt(delay);
            } catch (Exception e) {
                // Just use the default 1000 milliseconds as the default
            }
        }

        Class cls = orb.getORBData().getBadServerIdHandler();
        if (cls == null) {
            orb.setBadServerIdHandler(this);
        } else {
            orb.initBadServerIdHandler();
        }

        orb.connect(this);
        ProcessMonitorThread.start(serverTable);
    }

    public void activate(int serverId) throws ServerAlreadyActive, ServerNotRegistered, ServerHeldDown {

        ServerLocation location;
        ServerTableEntry entry;
        Integer key = new Integer(serverId);

        synchronized (serverTable) {
            entry = (ServerTableEntry) serverTable.get(key);
        }

        if (entry != null && entry.isActive()) {
            if (debug)
                System.out.println("ServerManagerImpl: activate for server Id " + serverId + " failed because server is already active. " + "entry = " + entry);

            throw new ServerAlreadyActive(serverId);
        }

        // locate the server
        try {

            // We call getEntry here so that state of the entry is
            // checked for validity before we actually go and locate a server

            entry = getEntry(serverId);

            if (debug)
                System.out.println("ServerManagerImpl: locateServer called with " + " serverId=" + serverId + " endpointType=" + IIOP_CLEAR_TEXT.value + " block=false");

            location = locateServer(entry, IIOP_CLEAR_TEXT.value, false);

            if (debug)
                System.out.println("ServerManagerImpl: activate for server Id " + serverId + " found location " + location.hostname + " and activated it");
        } catch (NoSuchEndPoint ex) {
            if (debug)
                System.out.println("ServerManagerImpl: activate for server Id " + " threw NoSuchEndpoint exception, which was ignored");
        }
    }

    public void active(int serverId, Server server) throws ServerNotRegistered {
        ServerTableEntry entry;
        Integer key = new Integer(serverId);

        synchronized (serverTable) {
            entry = (ServerTableEntry) serverTable.get(key);

            if (entry == null) {
                if (debug)
                    System.out.println("ServerManagerImpl: active for server Id " + serverId + " called, but no such server is registered.");

                throw wrapper.serverNotExpectedToRegister();
            } else {
                if (debug)
                    System.out.println("ServerManagerImpl: active for server Id " + serverId + " called.  This server is now active.");

                entry.register(server);
            }
        }
    }

    public void registerEndpoints(int serverId, String orbId, EndPointInfo[] endpointList) throws NoSuchEndPoint, ServerNotRegistered, ORBAlreadyRegistered {
        // orbId is ignored for now
        ServerTableEntry entry;
        Integer key = new Integer(serverId);

        synchronized (serverTable) {
            entry = (ServerTableEntry) serverTable.get(key);

            if (entry == null) {
                if (debug)
                    System.out.println("ServerManagerImpl: registerEndpoint for server Id " + serverId + " called, but no such server is registered.");

                throw wrapper.serverNotExpectedToRegister();
            } else {
                if (debug)
                    System.out.println("ServerManagerImpl: registerEndpoints for server Id " + serverId + " called.  This server is now active.");

                entry.registerPorts(orbId, endpointList);

            }
        }
    }

    public int[] getActiveServers() {
        ServerTableEntry entry;
        int[] list = null;

        synchronized (serverTable) {
            // unlike vectors, list is not synchronized

            ArrayList servers = new ArrayList(0);

            Iterator serverList = serverTable.keySet().iterator();

            try {
                while (serverList.hasNext()) {
                    Integer key = (Integer) serverList.next();
                    // get an entry
                    entry = (ServerTableEntry) serverTable.get(key);

                    if (entry.isValid() && entry.isActive()) {
                        servers.add(entry);
                    }
                }
            } catch (NoSuchElementException e) {
                // all done
            }

            // collect the active entries
            list = new int[servers.size()];
            for (int i = 0; i < servers.size(); i++) {
                entry = (ServerTableEntry) servers.get(i);
                list[i] = entry.getServerId();
            }
        }

        if (debug) {
            StringBuffer sb = new StringBuffer();
            for (int ctr = 0; ctr < list.length; ctr++) {
                sb.append(' ');
                sb.append(list[ctr]);
            }

            System.out.println("ServerManagerImpl: getActiveServers returns" + sb.toString());
        }

        return list;
    }

    public void shutdown(int serverId) throws ServerNotActive {
        ServerTableEntry entry;
        Integer key = new Integer(serverId);

        synchronized (serverTable) {
            entry = (ServerTableEntry) serverTable.remove(key);

            if (entry == null) {
                if (debug)
                    System.out.println("ServerManagerImpl: shutdown for server Id " + serverId + " throws ServerNotActive.");

                throw new ServerNotActive(serverId);
            }

            try {
                entry.destroy();

                if (debug)
                    System.out.println("ServerManagerImpl: shutdown for server Id " + serverId + " completed.");
            } catch (Exception e) {
                if (debug)
                    System.out.println("ServerManagerImpl: shutdown for server Id " + serverId + " threw exception " + e);
            }
        }
    }

    private ServerTableEntry getEntry(int serverId) throws ServerNotRegistered {
        Integer key = new Integer(serverId);
        ServerTableEntry entry = null;

        synchronized (serverTable) {
            entry = (ServerTableEntry) serverTable.get(key);

            if (debug)
                if (entry == null) {
                    System.out.println("ServerManagerImpl: getEntry: " + "no active server found.");
                } else {
                    System.out.println("ServerManagerImpl: getEntry: " + " active server found " + entry + ".");
                }

            if ((entry != null) && (!entry.isValid())) {
                serverTable.remove(key);
                entry = null;
            }

            if (entry == null) {
                ServerDef serverDef = repository.getServer(serverId);

                entry = new ServerTableEntry(wrapper, serverId, serverDef, initialPort, dbDirName, false, debug);
                serverTable.put(key, entry);
                entry.activate();
            }
        }

        return entry;
    }

    private ServerLocation locateServer(ServerTableEntry entry, String endpointType, boolean block) throws NoSuchEndPoint, ServerNotRegistered, ServerHeldDown {
        ServerLocation location = new ServerLocation();

        // if server location is desired, then wait for the server
        // to register back, then return location

        ORBPortInfo[] serverORBAndPortList;
        if (block) {
            try {
                serverORBAndPortList = entry.lookup(endpointType);
            } catch (Exception ex) {
                if (debug)
                    System.out.println("ServerManagerImpl: locateServer: " + "server held down");

                throw new ServerHeldDown(entry.getServerId());
            }

            String host = orb.getLegacyServerSocketManager().legacyGetEndpoint(LegacyServerSocketEndPointInfo.DEFAULT_ENDPOINT).getHostName();
            location.hostname = host;
            int listLength;
            if (serverORBAndPortList != null) {
                listLength = serverORBAndPortList.length;
            } else {
                listLength = 0;
            }
            location.ports = new ORBPortInfo[listLength];
            for (int i = 0; i < listLength; i++) {
                location.ports[i] = new ORBPortInfo(serverORBAndPortList[i].orbId, serverORBAndPortList[i].port);

                if (debug)
                    System.out.println("ServerManagerImpl: locateServer: " + "server located at location " + location.hostname + " ORBid  " + serverORBAndPortList[i].orbId + " Port " + serverORBAndPortList[i].port);
            }
        }

        return location;
    }

    private ServerLocationPerORB locateServerForORB(ServerTableEntry entry, String orbId, boolean block) throws InvalidORBid, ServerNotRegistered, ServerHeldDown {
        ServerLocationPerORB location = new ServerLocationPerORB();

        // if server location is desired, then wait for the server
        // to register back, then return location

        EndPointInfo[] endpointInfoList;
        if (block) {
            try {
                endpointInfoList = entry.lookupForORB(orbId);
            } catch (InvalidORBid ex) {
                throw ex;
            } catch (Exception ex) {
                if (debug)
                    System.out.println("ServerManagerImpl: locateServerForORB: " + "server held down");

                throw new ServerHeldDown(entry.getServerId());
            }

            String host = orb.getLegacyServerSocketManager().legacyGetEndpoint(LegacyServerSocketEndPointInfo.DEFAULT_ENDPOINT).getHostName();
            location.hostname = host;
            int listLength;
            if (endpointInfoList != null) {
                listLength = endpointInfoList.length;
            } else {
                listLength = 0;
            }
            location.ports = new EndPointInfo[listLength];
            for (int i = 0; i < listLength; i++) {
                location.ports[i] = new EndPointInfo(endpointInfoList[i].endpointType, endpointInfoList[i].port);

                if (debug)
                    System.out.println("ServerManagerImpl: locateServer: " + "server located at location " + location.hostname + " endpointType  " + endpointInfoList[i].endpointType + " Port " + endpointInfoList[i].port);
            }
        }

        return location;
    }

    public String[] getORBNames(int serverId) throws ServerNotRegistered {
        try {
            ServerTableEntry entry = getEntry(serverId);
            return (entry.getORBList());
        } catch (Exception ex) {
            throw new ServerNotRegistered(serverId);
        }
    }

    private ServerTableEntry getRunningEntry(int serverId) throws ServerNotRegistered {
        ServerTableEntry entry = getEntry(serverId);

        try {
            // this is to see if the server has any listeners
            ORBPortInfo[] serverORBAndPortList = entry.lookup(IIOP_CLEAR_TEXT.value);
        } catch (Exception exc) {
            return null;
        }
        return entry;

    }

    public void install(int serverId) throws ServerNotRegistered, ServerHeldDown, ServerAlreadyInstalled {
        ServerTableEntry entry = getRunningEntry(serverId);
        if (entry != null) {
            repository.install(serverId);
            entry.install();
        }
    }

    public void uninstall(int serverId) throws ServerNotRegistered, ServerHeldDown, ServerAlreadyUninstalled {
        ServerTableEntry entry = (ServerTableEntry) serverTable.get(new Integer(serverId));

        if (entry != null) {

            entry = (ServerTableEntry) serverTable.remove(new Integer(serverId));

            if (entry == null) {
                if (debug)
                    System.out.println("ServerManagerImpl: shutdown for server Id " + serverId + " throws ServerNotActive.");

                throw new ServerHeldDown(serverId);
            }

            entry.uninstall();
        }
    }

    public ServerLocation locateServer(int serverId, String endpointType) throws NoSuchEndPoint, ServerNotRegistered, ServerHeldDown {
        ServerTableEntry entry = getEntry(serverId);
        if (debug)
            System.out.println("ServerManagerImpl: locateServer called with " + " serverId=" + serverId + " endpointType=" + endpointType + " block=true");

        // passing in entry to eliminate multiple lookups for
        // the same entry in some cases

        return locateServer(entry, endpointType, true);
    }

    /** This method is used to obtain the registered ports for an ORB.
    * This is useful for custom Bad server ID handlers in ORBD.
    */
    public ServerLocationPerORB locateServerForORB(int serverId, String orbId) throws InvalidORBid, ServerNotRegistered, ServerHeldDown {
        ServerTableEntry entry = getEntry(serverId);

        // passing in entry to eliminate multiple lookups for
        // the same entry in some cases

        if (debug)
            System.out.println("ServerManagerImpl: locateServerForORB called with " + " serverId=" + serverId + " orbId=" + orbId + " block=true");
        return locateServerForORB(entry, orbId, true);
    }

    public void handle(ObjectKey okey) {
        IOR newIOR = null;
        ServerLocationPerORB location;

        // we need to get the serverid and the orbid from the object key
        ObjectKeyTemplate oktemp = okey.getTemplate();
        int serverId = oktemp.getServerId();
        String orbId = oktemp.getORBId();

        try {
            // get the ORBName corresponding to the orbMapid, that was
            // first registered by the server
            ServerTableEntry entry = getEntry(serverId);
            location = locateServerForORB(entry, orbId, true);

            if (debug)
                System.out.println("ServerManagerImpl: handle called for server id" + serverId + "  orbid  " + orbId);

            // we received a list of ports corresponding to an ORB in a
            // particular server, now retrieve the one corresponding
            // to IIOP_CLEAR_TEXT, and for other created the tagged
            // components to be added to the IOR

            int clearPort = 0;
            EndPointInfo[] listenerPorts = location.ports;
            for (int i = 0; i < listenerPorts.length; i++) {
                if ((listenerPorts[i].endpointType).equals(IIOP_CLEAR_TEXT.value)) {
                    clearPort = listenerPorts[i].port;
                    break;
                }
            }

            // create a new IOR with the correct port and correct tagged
            // components
            IIOPAddress addr = IIOPFactories.makeIIOPAddress(orb, location.hostname, clearPort);
            IIOPProfileTemplate iptemp = IIOPFactories.makeIIOPProfileTemplate(orb, GIOPVersion.V1_2, addr);
            if (GIOPVersion.V1_2.supportsIORIIOPProfileComponents()) {
                iptemp.add(IIOPFactories.makeCodeSetsComponent(orb));
                iptemp.add(IIOPFactories.makeMaxStreamFormatVersionComponent());
            }
            IORTemplate iortemp = IORFactories.makeIORTemplate(oktemp);
            iortemp.add(iptemp);

            newIOR = iortemp.makeIOR(orb, "IDL:org/omg/CORBA/Object:1.0", okey.getId());
        } catch (Exception e) {
            throw wrapper.errorInBadServerIdHandler(e);
        }

        if (debug)
            System.out.println("ServerManagerImpl: handle " + "throws ForwardException");

        try {
            // This delay is required in case of Server is activated or
            // re-activated the first time. Server needs some time before
            // handling all the requests.
            // (Talk to Ken to see whether there is a better way of doing this).
            Thread.sleep(serverStartupDelay);
        } catch (Exception e) {
            System.out.println("Exception = " + e);
            e.printStackTrace();
        }

        throw new ForwardException(orb, newIOR);
    }

    public int getEndpoint(String endpointType) throws NoSuchEndPoint {
        return orb.getLegacyServerSocketManager().legacyGetTransientServerPort(endpointType);
    }

    public int getServerPortForType(ServerLocationPerORB location, String endPointType) throws NoSuchEndPoint {
        EndPointInfo[] listenerPorts = location.ports;
        for (int i = 0; i < listenerPorts.length; i++) {
            if ((listenerPorts[i].endpointType).equals(endPointType)) {
                return listenerPorts[i].port;
            }
        }
        throw new NoSuchEndPoint();
    }

}
